/**
 * Software License, Version 1.0
 * 
 * Copyright 2003 The Trustees of Indiana University.  All rights reserved.
 * 
 *
 *Redistribution and use in source and binary forms, with or without 
 *modification, are permitted provided that the following conditions are met:
 *
 *1) All redistributions of source code must retain the above copyright notice,
 * the list of authors in the original source code, this list of conditions and
 * the disclaimer listed in this license;
 *2) All redistributions in binary form must reproduce the above copyright 
 * notice, this list of conditions and the disclaimer listed in this license in
 * the documentation and/or other materials provided with the distribution;
 *3) Any documentation included with all redistributions must include the 
 * following acknowledgement:
 *
 *"This product includes software developed by the Community Grids Lab. For 
 * further information contact the Community Grids Lab at 
 * http://communitygrids.iu.edu/."
 *
 * Alternatively, this acknowledgement may appear in the software itself, and 
 * wherever such third-party acknowledgments normally appear.
 * 
 *4) The name Indiana University or Community Grids Lab or NaradaBrokering, 
 * shall not be used to endorse or promote products derived from this software 
 * without prior written permission from Indiana University.  For written 
 * permission, please contact the Advanced Research and Technology Institute 
 * ("ARTI") at 351 West 10th Street, Indianapolis, Indiana 46202.
 *5) Products derived from this software may not be called NaradaBrokering, 
 * nor may Indiana University or Community Grids Lab or NaradaBrokering appear
 * in their name, without prior written permission of ARTI.
 * 
 *
 * Indiana University provides no reassurances that the source code provided 
 * does not infringe the patent or any other intellectual property rights of 
 * any other entity.  Indiana University disclaims any liability to any 
 * recipient for claims brought by any other entity based on infringement of 
 * intellectual property rights or otherwise.  
 *
 *LICENSEE UNDERSTANDS THAT SOFTWARE IS PROVIDED "AS IS" FOR WHICH NO 
 *WARRANTIES AS TO CAPABILITIES OR ACCURACY ARE MADE. INDIANA UNIVERSITY GIVES
 *NO WARRANTIES AND MAKES NO REPRESENTATION THAT SOFTWARE IS FREE OF 
 *INFRINGEMENT OF THIRD PARTY PATENT, COPYRIGHT, OR OTHER PROPRIETARY RIGHTS. 
 *INDIANA UNIVERSITY MAKES NO WARRANTIES THAT SOFTWARE IS FREE FROM "BUGS", 
 *"VIRUSES", "TROJAN HORSES", "TRAP DOORS", "WORMS", OR OTHER HARMFUL CODE.  
 *LICENSEE ASSUMES THE ENTIRE RISK AS TO THE PERFORMANCE OF SOFTWARE AND/OR 
 *ASSOCIATED MATERIALS, AND TO THE PERFORMANCE AND VALIDITY OF INFORMATION 
 *GENERATED USING SOFTWARE.
 */
package cgl.narada.wsinfra.benchmark;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Arrays;
import java.util.Date;
import java.util.Vector;

import cgl.narada.service.client.ClientService;
import cgl.narada.service.client.SessionService;
import cgl.narada.service.timer.ClockI;
import cgl.narada.service.timer.HRClock;
import cgl.narada.util.StatUtils;

/** This is a class which is responsible for timing various operations
    performed within the BenchmarkOperations class. This class uses the
    high resolution timer available within NaradaBrokering.

    @author Shrideep Pallickara
    $Date: 2005/07/29 22:41:29 $
    $Revision: 1.5 $
*/

public class BenchmarkTimer {

  private BenchmarkOperations benchmarkOperations;
  private BenchmarkConstants benchmarkConstants;
  private Runtime runtime;
  private ClockI hrClock;
  private Vector reports;
  
  private BufferedWriter bufferedWriter;
  private String moduleName = "BenchmarkTimer: ";
  
  public BenchmarkTimer() throws Exception { 
    initializeHRClock();
    benchmarkOperations = BenchmarkOperations.getInstance();
    benchmarkConstants = BenchmarkConstants.getInstance();
 
    runtime = Runtime.getRuntime();
    reports = new Vector();
  }


  private void initializeHRClock() throws Exception {
    int entityId = 1000;
    /** This file is used to specify the location of the native NB libraries
	that are used for computing the high resolution timestamps. */ 
    String configFile ="C:/NB/NaradaBrokering/config/ServiceConfiguration.txt";
    //String configFile ="/home/shrideep/nb/config/ServiceConfiguration.txt"; 
    SessionService.setServiceConfigurationLocation(configFile);
    ClientService clientService = SessionService.getClientService(entityId);
    hrClock = HRClock.getClock();
    
    System.out.println("\n\n\n\n");
  }

  private void initializeReportFile(String fileName) throws IOException {
	  bufferedWriter = new BufferedWriter(new FileWriter(new File(fileName)));
	  System.out.println(moduleName + "Initialized Subscriber Report File [" + 
			  fileName + "]"); 
	  String initString = "This Benchmark was run on the following day/time "+
	  		new Date() + "\n\n";	
	  writeToReportFile(initString);
  }
	  
	  
	private void writeToReportFile(String reportLine) {
	 try {
	   bufferedWriter.write(reportLine, 0, reportLine.length());
	   bufferedWriter.newLine();
	   bufferedWriter.flush();
	 } catch (IOException ioe) {
		 System.out.println(moduleName + "Error writing file ...... ");
	 } 
	}
  

  private void performOperation(int operation) throws Exception {
    boolean isValidOperation = benchmarkConstants.isValidOperation(operation);
    if (!isValidOperation) {
      String errorReport = moduleName + "Specified operation [" + operation + 
	"] is not a valid operation.";
      throw new Exception(errorReport);
    }

    
    if (operation == BenchmarkConstants.CREATE_ENVELOPE) {
      benchmarkOperations.createEnvelopeDocument();
      return;
    }

    if (operation == BenchmarkConstants.CREATE_SOAPMESSAGE) {
      benchmarkOperations.createSOAPMessage();
      return;
    }


    if (operation == BenchmarkConstants.CONVERT_ENVELOPE_DOCUMENT) {
      benchmarkOperations.convertEnvelopeDocument();
      return;
    }

    if (operation == BenchmarkConstants.CONVERT_SOAP_MESSAGE) {
      benchmarkOperations.convertSOAPMessage();
      return;
    }


    if (operation == BenchmarkConstants.CREATE_EPR) {
      benchmarkOperations.createEpr();
      return;
    }

    if (operation == BenchmarkConstants.CREATE_EPR_WITH_REF_PROPS) {
      benchmarkOperations.createEprWithReferenceProperties();
      return;
    }

    if (operation == BenchmarkConstants.CREATE_EPR_ENVELOPE) {
      benchmarkOperations.createEprEnvelope();
      return;  
    }

    if (operation == 
   	 	BenchmarkConstants.CREATE_EPR_ENVELOPE_WITH_MOST_WSA_FIELDS) {
      benchmarkOperations.createEprEnvelopeWithMostWSAFields();  
      return;  
    }


    if (operation == BenchmarkConstants.PARSE_WSA_HEADERS) {
      benchmarkOperations.parseWsaHeaders();
      return; 
    }

    if (operation == BenchmarkConstants.CREATE_WSRM_FAULT) {
      benchmarkOperations.createSequenceTerminatedFault();
      return; 
    }

    
    if (operation == BenchmarkConstants.CREATE_WSRM_CREATE_SEQUENCE) {
      benchmarkOperations.createCreateSequenceRequest();
      return;
    }
    if (operation == BenchmarkConstants.CREATE_WSRM_CREATE_SEQUENCE_RESPONSE) {
      benchmarkOperations.createCreateSequenceResponse();
      return;
    }
    
    if (operation == BenchmarkConstants.CREATE_WSRM_SEQUENCE_DOCUMENT) {
      benchmarkOperations.createSequenceDocument();
      return;
    }
    
    if (operation == BenchmarkConstants.ADD_WSRM_SEQUENCE_DOCUMENT) {
      benchmarkOperations.addSequenceInfoToEnvelope();
      return;
    }
    
    if (operation == BenchmarkConstants.CREATE_SEQUENCE_ACKNOWLEDGEMENT) {
      benchmarkOperations.createSequenceAcknowledgement();
      return;
    }
    
    if (operation == BenchmarkConstants.CREATE_TERMINATE_SEQUENCE) {
      benchmarkOperations.createTerminateSequence();
      return;
    }
    
    if (operation == BenchmarkConstants.CREATE_SUBSCRIBE_REQUEST) {
       benchmarkOperations.createSubscribeRequest();
       return;
     }
    
    
    if (operation == BenchmarkConstants.CREATE_SUBSCRIBE_RESPONSE) {
       benchmarkOperations.createSubscribeResponse();
       return;
     }
    
    if (operation == BenchmarkConstants.CREATE_RENEW_REQUEST) {
       benchmarkOperations.createRenewRequest();
       return;
     }
    
    
    if (operation == BenchmarkConstants.CREATE_RENEW_RESPONSE) {
       benchmarkOperations.createRenewResponse();
       return;
     }
    
    if (operation == BenchmarkConstants.CREATE_GET_STATUS_REQUEST) {
       benchmarkOperations.createGetStatus();
       return;
     }
    
    
    if (operation == BenchmarkConstants.CREATE_GET_STATUS_RESPONSE) {
       benchmarkOperations.createGetStatusResponse();
       return;
     }
    
    if (operation == BenchmarkConstants.CREATE_UNSUBSCRIBE_REQUEST) {
       benchmarkOperations.createUnsubscribe();
       return;
     }
    
    
    if (operation == BenchmarkConstants.CREATE_UNSUBSCRIBE_RESPONSE) {
       benchmarkOperations.createUnsubscribeResponse();
       return;
     }
    
    
    if (operation == BenchmarkConstants.CREATE_SUBSCRIPTION_END) {
       benchmarkOperations.createSubscriptionEnd();
       return;
     }
   
    
    if (operation == BenchmarkConstants.PERFORM_XPATH_MATCHING) {
   	 benchmarkOperations.performXPathMatching();
       return;
     }
     
     if (operation == BenchmarkConstants.PERFORM_TOPIC_MATCHING) {
   	  benchmarkOperations.performTopicMatching();
        return;
     }
     
     if (operation == BenchmarkConstants.PERFORM_REGEX_MATCHING) {
   	  benchmarkOperations.performRegExMatching();
        return;
     }
     
     if (operation == BenchmarkConstants.PERFORM_XQUERY_MATCHING) {
   	  benchmarkOperations.performXQueryMatching();
        return;
     }
     
    
    String errorReport = moduleName + "Unable to perform operation [" +
      operation + "]. There is no corresponding operation in the " + 
      "BenchmarkOperations";
    
    throw new Exception(errorReport);
  }


  public void timeOperations(int operation) throws Exception {
    System.out.println("\n\n\n\n");
    int numOfTimes = 100;
    performOperation(operation);
    

    long [] delays = new long[numOfTimes];
    long [] utilizations = new long[numOfTimes];
    for (int i=0; i < numOfTimes; i++) {
      long freeMemory = runtime.freeMemory();
      long start = hrClock.getTimeMicroseconds();
      performOperation(operation);
      long nowAvailable = runtime.freeMemory();
      long end = hrClock.getTimeMicroseconds();
      
      long delay = (end - start);
      delays[i] = delay;

      long utilization = freeMemory - nowAvailable;
      utilizations[i] = utilization;
    }
    
    
    for (int i=0; i <delays.length; i++) {
      System.out.println(i + "\t" + delays[i] + "     \t\t" + utilizations[i]);
    }

    long[] delaysWithoutOutliers = 
      StatUtils.getValuesWithoutOutliers(delays, delays.length);
    
    /*delaysWithoutOutliers = 
      StatUtils.getValuesWithoutOutliers(delaysWithoutOutliers, 
					 delaysWithoutOutliers.length);*/
 
    int numOfOutliers = delays.length - delaysWithoutOutliers.length;
    
    double mean = StatUtils.computeMean(delaysWithoutOutliers,
					delaysWithoutOutliers.length, 0);
    double stdDev = 
      StatUtils.computeStandardDeviation(delaysWithoutOutliers,
					 delaysWithoutOutliers.length, 0);
    
    double stdError =
      StatUtils.computeStandardErrorOfMean(stdDev, 
					   delaysWithoutOutliers.length);

    int[] indicesOfOutliers = computeIndicesOfDiscardedOutliers(delays, delaysWithoutOutliers);
    if (indicesOfOutliers != null) {
   	 for (int i = 0; i < indicesOfOutliers.length; i++) {
			System.out.println(indicesOfOutliers[i]);
		}
    }
    long [] memValuesWithoutOutliers =
   	 retriveValuesWithoutIndices(utilizations, indicesOfOutliers);
    
    for (int i = 0; i < delaysWithoutOutliers.length; i++) {
   	 String reportRow = (i+1) + " \t\t" + delaysWithoutOutliers[i] +
			" \t\t" + memValuesWithoutOutliers[i];
		System.out.println(reportRow);
		writeToReportFile(reportRow);
	}
    
    Arrays.sort(delaysWithoutOutliers);

    
    String report = moduleName + "Results for Operation [" + 
      benchmarkConstants.getOperationName(operation) + "] \n" + 
      "Number of outliers = (" + numOfOutliers + 
      ") => " + printOutliers(delays, numOfOutliers) + 
      "\nMean = " + mean + ",  standard deviation = " + stdDev +  
      "\n standard error = " + stdError
      + "\n min value = " + delaysWithoutOutliers[0] + ", max value = " + 
      delaysWithoutOutliers[delaysWithoutOutliers.length -1 ] ;
    
    System.out.println(report);
    
    String reportLine = benchmarkConstants.getOperationName(operation) + "   "
   	 + mean + "   " + stdDev + "   " + stdError + "   " + numOfOutliers + 
   	 "   " + delaysWithoutOutliers[0] + "   " + 
   	 delaysWithoutOutliers[delaysWithoutOutliers.length -1 ] + "\n\n\n\n";
    
    
    String reportEntry = benchmarkConstants.getOperationName(operation) + " " +
      trimDecimalPlaces(mean + "")  + " " + 
      trimDecimalPlaces(stdDev + "")  + " " + 
      trimDecimalPlaces(stdError + "") + " " + 
      numOfOutliers + " " +
      delaysWithoutOutliers[0] + " " + 
      delaysWithoutOutliers[delaysWithoutOutliers.length -1];
    
    reports.add(reportEntry);
    
    System.out.println(reportLine);
    writeToReportFile(reportLine);
  }



  private String printOutliers(long[] delays, int numOfOutliers) {
    Arrays.sort(delays);

    String outlierInfo = "";

    int index = delays.length - 1;
    for (int i=0; i < numOfOutliers; i++) {
      outlierInfo += ( "[" + delays[index - i] + "] "  );
    }    
    outlierInfo += ".";
    return outlierInfo;
  }

  
  private void printFinalReport() {
	  System.out.println("Operation   Mean   StdDev   StdError	Outlier	Min	Max" +
			  "Mem (Bytes)");
	  
	  for (int i=0; i < reports.size(); i++) {
		  System.out.println(reports.elementAt(i));
		  writeToReportFile(reports.elementAt(i).toString());
	  }
  }
  
  private int[] 
  computeIndicesOfDiscardedOutliers(long[] withOutliers, long[] noOutliers) {
	  int numOfOutliers = withOutliers.length - noOutliers.length;
	  int[] indices = null;
	  
	  if (numOfOutliers == 0) {
		  return null;
	  }
	  
	  indices = new int[numOfOutliers];
	  
	  int tracker = 0;
	  for (int i = 0, j=0; i < withOutliers.length; i++) {
		 if (withOutliers[i] != noOutliers[j]) {
			 indices[tracker] = i;
			 tracker++;
		 } else {
			 j++;
			 if (j == noOutliers.length) {
				 /** This implies that the remaining elements in the withOutliers
				  * array are outliers. We need to note their indices too.
				  */
				 boolean keepLooping = true;
				 while(keepLooping) {
					 i++;
					 if (i >= withOutliers.length ) {
						 keepLooping = false;
					 } else {
						 indices[tracker] = i;
						 tracker++;
					 } /* end if-else*/
				 } /*end while(keepLooping) */
			 }
		 }
	  }
	  
	  return indices;
  }
  
  
  private long[] 
  retriveValuesWithoutIndices(long[] originalArray, int[] indices) {
	long[] result = null;
	
	if (indices == null) {
		return originalArray;
	}
	
	int arraySize = originalArray.length - indices.length;
	if (arraySize == 0) {
		return originalArray;
	}
	
	result = new long[arraySize];
	int resultIndex =0; 
	for (int i = 0; i < originalArray.length; i++) {
		boolean ignoreI = false;
		for (int j = 0; j < indices.length; j++) {
			if (i == indices[j]) {
				ignoreI = true;
			}
		}
		if (!ignoreI) {
			result[resultIndex] = originalArray[i];
			resultIndex++;
		}
	}
	
	return result;
  }
  
  private String trimDecimalPlaces(String value) {
	  String decimalPoint = ".";
	  int decimalPlaces = 3;
	  int decimalPosition = value.indexOf(decimalPoint);
	  if (decimalPosition == -1) {
		  /** There is no decimal point in this string. */
		  return value;
	  }
	  
	  /** If the value if of the form 131.25 or 131.255 simply return
	   * 131.25 (3+4 > 6) 131.25 (3 + 3 + 1 = 7)
	   * */
	  if (decimalPosition + decimalPlaces + 1 >= value.length()) {
		  return value;
	  }
	  
	  
	  String truncatedValue = value.substring(0, decimalPosition + decimalPlaces + 1);
	  return truncatedValue;	  
  }
  
  public static void main(String[] args) {
    try {
      BenchmarkTimer bmTimer = new BenchmarkTimer();
      Date date = new Date();
      String fileName = "BM-Report-Y" + date.getYear() + "D" + date.getDay() + "H" +
      date.getHours() + "M" + date.getMinutes()+ ".txt";
      bmTimer.initializeReportFile(fileName);
      Thread.sleep(5000);
      
      bmTimer.timeOperations(BenchmarkConstants.CREATE_ENVELOPE);
      bmTimer.timeOperations(BenchmarkConstants.CREATE_SOAPMESSAGE);
      bmTimer.timeOperations(BenchmarkConstants.CONVERT_ENVELOPE_DOCUMENT);
      bmTimer.timeOperations(BenchmarkConstants.CONVERT_SOAP_MESSAGE);
      bmTimer.timeOperations(BenchmarkConstants.CREATE_EPR);
      bmTimer.timeOperations(BenchmarkConstants.CREATE_EPR_WITH_REF_PROPS);
      
      bmTimer.timeOperations(BenchmarkConstants.CREATE_EPR_ENVELOPE);
      bmTimer.timeOperations(BenchmarkConstants.CREATE_EPR_ENVELOPE_WITH_MOST_WSA_FIELDS);
      bmTimer.timeOperations(BenchmarkConstants.PARSE_WSA_HEADERS);
      bmTimer.timeOperations(BenchmarkConstants.CREATE_WSRM_FAULT);

      /*bmTimer.timeOperations(BenchmarkConstants.CREATE_WSRM_CREATE_SEQUENCE);
      bmTimer.timeOperations(BenchmarkConstants.CREATE_WSRM_CREATE_SEQUENCE_RESPONSE);
      bmTimer.timeOperations(BenchmarkConstants.CREATE_WSRM_SEQUENCE_DOCUMENT);
      bmTimer.timeOperations(BenchmarkConstants.ADD_WSRM_SEQUENCE_DOCUMENT);

      bmTimer.timeOperations(BenchmarkConstants.CREATE_SEQUENCE_ACKNOWLEDGEMENT);
      bmTimer.timeOperations(BenchmarkConstants.CREATE_TERMINATE_SEQUENCE);
      */
      
      
      bmTimer.timeOperations(BenchmarkConstants.CREATE_SUBSCRIBE_REQUEST);
      bmTimer.timeOperations(BenchmarkConstants.CREATE_SUBSCRIBE_RESPONSE);
      bmTimer.timeOperations(BenchmarkConstants.CREATE_UNSUBSCRIBE_REQUEST);
      bmTimer.timeOperations(BenchmarkConstants.CREATE_UNSUBSCRIBE_RESPONSE);
      bmTimer.timeOperations(BenchmarkConstants.CREATE_RENEW_REQUEST);
      bmTimer.timeOperations(BenchmarkConstants.CREATE_RENEW_RESPONSE);
      bmTimer.timeOperations(BenchmarkConstants.CREATE_GET_STATUS_REQUEST);
      bmTimer.timeOperations(BenchmarkConstants.CREATE_GET_STATUS_RESPONSE);
      bmTimer.timeOperations(BenchmarkConstants.CREATE_SUBSCRIPTION_END);

		
      bmTimer.timeOperations(BenchmarkConstants.PERFORM_XPATH_MATCHING);
      bmTimer.timeOperations(BenchmarkConstants.PERFORM_TOPIC_MATCHING);
      bmTimer.timeOperations(BenchmarkConstants.PERFORM_REGEX_MATCHING);
      //bmTimer.timeOperations(BenchmarkConstants.PERFORM_XQUERY_MATCHING);
      
      bmTimer.printFinalReport();
      
      
      /*System.out.println(bmTimer.trimDecimalPlaces("192.8695652173913"));
      System.out.println(bmTimer.trimDecimalPlaces("192.8721"));
      System.out.println(bmTimer.trimDecimalPlaces("192.867"));
      System.out.println(bmTimer.trimDecimalPlaces("192"));
      System.out.println(bmTimer.trimDecimalPlaces("0.512"));
      System.out.println(bmTimer.trimDecimalPlaces("0.52575"));
      System.out.println(bmTimer.trimDecimalPlaces("0.002"));*/
      
    } catch (Exception e) {
      String errorReport = "Encountered problems ... " + e.toString() + "\n";
      System.out.println(errorReport);
      e.printStackTrace();
    }
  }

}
